package com.zlyx.easy.api.doc;

import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.github.javaparser.javadoc.Javadoc;
import com.github.javaparser.javadoc.JavadocBlockTag;
import com.github.javaparser.javadoc.description.JavadocDescriptionElement;
import com.zlyx.easy.api.model.ApiModule;
import com.zlyx.easy.core.collections.Lists;
import com.zlyx.easy.core.map.Maps;
import com.zlyx.easy.core.tool.StringFormat;
import com.zlyx.easy.core.utils.FileUtils;
import com.zlyx.easy.core.utils.MethodUtils;

public class DocReader {

	static String tagName = null;
	static String author = null;
	static String since = null;

	/**
	 * 
	 * @param path
	 * @return
	 * @throws Exception
	 */
	public static Map<String, ApiModule> parse(String path) {
		List<String> files = FileUtils.getJavaFiles(path);
		Map<String, ApiModule> moduleMap = Maps.newMap();
		for (String file : files) {
			tagName = null;
			author = null;
			since = null;
			try (FileInputStream in = new FileInputStream(file)) {
				CompilationUnit cu = JavaParser.parse(in);
				if (cu.getTypes().size() <= 0) {
					continue;
				}
				TypeDeclaration<?> typeDeclaration = cu.getTypes().get(0);
				final Class<?> moduleType = Class.forName(
						cu.getPackageDeclaration().get().getNameAsString() + "." + typeDeclaration.getNameAsString());
				// 处理类注释
				if (typeDeclaration.getComment().isPresent()) {
					List<String> comments = Lists.newList();
					Optional<Javadoc> javadoc = typeDeclaration.getJavadoc();
					if (javadoc.isPresent()) {
						for (JavadocDescriptionElement element : javadoc.get().getDescription().getElements()) {
							comments.add(element.toText());
						}
						tagName = StringFormat.format(comments);
					}
					if (javadoc.get().getBlockTags() != null) {
						for (JavadocBlockTag tag : javadoc.get().getBlockTags()) {
							if (tag.getContent() != null) {
								String docName = tag.getTagName().toLowerCase();
								if (docName.contains("auth") || docName.contains("作者")) {
									author = tag.getContent().toText();
								}
//								if (docName.contains("desc")) {
//									module.setDescription(tag.getContent().toText());
//								}
								if (docName.contains("date") || tag.getTagName().toLowerCase().contains("since")
										|| docName.contains("时间") || docName.contains("日期")) {
									since = tag.getContent().toText();
								}
							}
						}
					}
				}
				new VoidVisitorAdapter<Void>() {
					@Override
					public void visit(MethodDeclaration methodDeclaration, Void arg) {
						Method method = parseToMethod(moduleType, methodDeclaration);
						if (method == null) {
							return;
						}
						ApiModule module = new ApiModule(method);
						module.setTag(tagName);
						List<String> comments = Lists.newList();
						Optional<Javadoc> javadoc = methodDeclaration.getJavadoc();
						if (javadoc.isPresent()) {
							for (JavadocDescriptionElement element : javadoc.get().getDescription().getElements()) {
								comments.add(element.toText());
							}
							module.setSummary(StringFormat.format(comments, "\n"));
						}
						if (javadoc.isPresent() && javadoc.get().getBlockTags() != null) {
							for (JavadocBlockTag tag : javadoc.get().getBlockTags()) {
								if (tag.getContent() != null) {
									String docName = tag.getTagName().toLowerCase();
									if (docName.contains("auth") || docName.contains("作者")) {
										module.setAuthor(tag.getContent().toText());
									}
									if (docName.contains("desc") || docName.contains("描述")) {
										module.setDescription(tag.getContent().toText());
									}
									if (docName.contains("date") || tag.getTagName().toLowerCase().contains("since")
											|| docName.contains("时间") || docName.contains("日期")) {
										module.setDate(tag.getContent().toText());
									}
									if (docName.contains("param")) {
										module.addParam(tag.getName().get(), tag.getContent().toText());
									}
								}
							}
						}
						if (module.getAuthor() == null) {
							module.setAuthor(author);
						}
						if (module.getDate() == null) {
							module.setDate(since);
						}
						moduleMap.put(MethodUtils.getHelpfulName(method), module);
						super.visit(methodDeclaration, arg);
					}
				}.visit(cu, null);
			} catch (Exception e) {
				continue;
			}
		}
		return moduleMap;
	}

	/**
	 * 获取指定方法的所有入参类型,便于反射
	 *
	 * @param declaration
	 * @return
	 */
	private static Method parseToMethod(Class<?> type, MethodDeclaration declaration) {
		List<Parameter> parameters = declaration.getParameters();
		parameters = parameters == null ? new ArrayList<Parameter>(0) : parameters;
		Method[] methods = type.getDeclaredMethods();
		for (Method m : methods) {
			if (!m.getName().equals(declaration.getNameAsString())) {
				continue;
			}
			if (m.getParameterTypes().length != parameters.size()) {
				continue;
			}
			boolean b = true;
			for (int j = 0; j < m.getParameterTypes().length; j++) {
				Class<?> paramClass = m.getParameterTypes()[j];
				Type ptype = parameters.get(j).getType();
				if (ptype == null) {
					continue;
				}
				String paranTypeName = ptype.toString();
				int index = paranTypeName.lastIndexOf(".");
				if (index > 0) {
					paranTypeName = paranTypeName.substring(index + 1);
				}
				// 处理泛型
				index = paranTypeName.indexOf("<");
				if (index > 0) {
					paranTypeName = paranTypeName.substring(0, index);
				}
				if (!paramClass.getSimpleName().equals(paranTypeName)) {
					b = false;
					break;
				}
			}
			if (b) {
				return m;
			}
		}
		return null;
	}
}
